1   /*
2    * Copyright (C) 2007 The Guava Authors
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.google.common.collect;
18  
19  import com.google.common.annotations.GwtCompatible;
20  import com.google.common.annotations.GwtIncompatible;
21  import com.google.common.testing.GcFinalization;
22  
23  import junit.framework.TestCase;
24  
25  import java.lang.ref.WeakReference;
26  import java.util.Iterator;
27  import java.util.NoSuchElementException;
28  
29  /**
30   * Unit test for {@code AbstractIterator}.
31   *
32   * @author Kevin Bourrillion
33   */
34  @SuppressWarnings("serial") // No serialization is used in this test
35  @GwtCompatible(emulated = true)
36  // TODO(cpovirk): why is this slow (>1m/test) under GWT when fully optimized?
37  public class AbstractIteratorTest extends TestCase {
38  
39    public void testDefaultBehaviorOfNextAndHasNext() {
40  
41      // This sample AbstractIterator returns 0 on the first call, 1 on the
42      // second, then signals that it's reached the end of the data
43      Iterator<Integer> iter = new AbstractIterator<Integer>() {
44        private int rep;
45        @Override public Integer computeNext() {
46          switch (rep++) {
47            case 0:
48              return 0;
49            case 1:
50              return 1;
51            case 2:
52              return endOfData();
53            default:
54              fail("Should not have been invoked again");
55              return null;
56          }
57        }
58      };
59  
60      assertTrue(iter.hasNext());
61      assertEquals(0, (int) iter.next());
62  
63      // verify idempotence of hasNext()
64      assertTrue(iter.hasNext());
65      assertTrue(iter.hasNext());
66      assertTrue(iter.hasNext());
67      assertEquals(1, (int) iter.next());
68  
69      assertFalse(iter.hasNext());
70  
71      // Make sure computeNext() doesn't get invoked again
72      assertFalse(iter.hasNext());
73  
74      try {
75        iter.next();
76        fail("no exception thrown");
77      } catch (NoSuchElementException expected) {
78      }
79    }
80  
81    public void testDefaultBehaviorOfPeek() {
82      /*
83       * This sample AbstractIterator returns 0 on the first call, 1 on the
84       * second, then signals that it's reached the end of the data
85       */
86      AbstractIterator<Integer> iter = new AbstractIterator<Integer>() {
87        private int rep;
88        @Override public Integer computeNext() {
89          switch (rep++) {
90            case 0:
91              return 0;
92            case 1:
93              return 1;
94            case 2:
95              return endOfData();
96            default:
97              fail("Should not have been invoked again");
98              return null;
99          }
100       }
101     };
102 
103     assertEquals(0, (int) iter.peek());
104     assertEquals(0, (int) iter.peek());
105     assertTrue(iter.hasNext());
106     assertEquals(0, (int) iter.peek());
107     assertEquals(0, (int) iter.next());
108 
109     assertEquals(1, (int) iter.peek());
110     assertEquals(1, (int) iter.next());
111 
112     try {
113       iter.peek();
114       fail("peek() should throw NoSuchElementException at end");
115     } catch (NoSuchElementException expected) {
116     }
117 
118     try {
119       iter.peek();
120       fail("peek() should continue to throw NoSuchElementException at end");
121     } catch (NoSuchElementException expected) {
122     }
123 
124     try {
125       iter.next();
126       fail("next() should throw NoSuchElementException as usual");
127     } catch (NoSuchElementException expected) {
128     }
129 
130     try {
131       iter.peek();
132       fail("peek() should still throw NoSuchElementException after next()");
133     } catch (NoSuchElementException expected) {
134     }
135   }
136 
137   @GwtIncompatible("weak references")
138   public void testFreesNextReference() {
139     Iterator<Object> itr = new AbstractIterator<Object>() {
140       @Override public Object computeNext() {
141         return new Object();
142       }
143     };
144     WeakReference<Object> ref = new WeakReference<Object>(itr.next());
145     GcFinalization.awaitClear(ref);
146   }
147 
148   public void testDefaultBehaviorOfPeekForEmptyIteration() {
149 
150     AbstractIterator<Integer> empty = new AbstractIterator<Integer>() {
151       private boolean alreadyCalledEndOfData;
152       @Override public Integer computeNext() {
153         if (alreadyCalledEndOfData) {
154           fail("Should not have been invoked again");
155         }
156         alreadyCalledEndOfData = true;
157         return endOfData();
158       }
159     };
160 
161     try {
162       empty.peek();
163       fail("peek() should throw NoSuchElementException at end");
164     } catch (NoSuchElementException expected) {
165     }
166 
167     try {
168       empty.peek();
169       fail("peek() should continue to throw NoSuchElementException at end");
170     } catch (NoSuchElementException expected) {
171     }
172   }
173 
174   public void testSneakyThrow() throws Exception {
175     Iterator<Integer> iter = new AbstractIterator<Integer>() {
176       boolean haveBeenCalled;
177       @Override public Integer computeNext() {
178         if (haveBeenCalled) {
179           fail("Should not have been called again");
180         } else {
181           haveBeenCalled = true;
182           sneakyThrow(new SomeCheckedException());
183         }
184         return null; // never reached
185       }
186     };
187 
188     // The first time, the sneakily-thrown exception comes out
189     try {
190       iter.hasNext();
191       fail("No exception thrown");
192     } catch (Exception e) {
193       if (!(e instanceof SomeCheckedException)) {
194         throw e;
195       }
196     }
197 
198     // But the second time, AbstractIterator itself throws an ISE
199     try {
200       iter.hasNext();
201       fail("No exception thrown");
202     } catch (IllegalStateException expected) {
203     }
204   }
205 
206   public void testException() {
207     final SomeUncheckedException exception = new SomeUncheckedException();
208     Iterator<Integer> iter = new AbstractIterator<Integer>() {
209       @Override public Integer computeNext() {
210         throw exception;
211       }
212     };
213 
214     // It should pass through untouched
215     try {
216       iter.hasNext();
217       fail("No exception thrown");
218     } catch (SomeUncheckedException e) {
219       assertSame(exception, e);
220     }
221   }
222 
223   public void testExceptionAfterEndOfData() {
224     Iterator<Integer> iter = new AbstractIterator<Integer>() {
225       @Override public Integer computeNext() {
226         endOfData();
227         throw new SomeUncheckedException();
228       }
229     };
230     try {
231       iter.hasNext();
232       fail("No exception thrown");
233     } catch (SomeUncheckedException expected) {
234     }
235   }
236 
237   public void testCantRemove() {
238     Iterator<Integer> iter = new AbstractIterator<Integer>() {
239       boolean haveBeenCalled;
240       @Override public Integer computeNext() {
241         if (haveBeenCalled) {
242           endOfData();
243         }
244         haveBeenCalled = true;
245         return 0;
246       }
247     };
248 
249     assertEquals(0, (int) iter.next());
250 
251     try {
252       iter.remove();
253       fail("No exception thrown");
254     } catch (UnsupportedOperationException expected) {
255     }
256   }
257 
258   public void testReentrantHasNext() {
259     Iterator<Integer> iter = new AbstractIterator<Integer>() {
260       @Override protected Integer computeNext() {
261         hasNext();
262         return null;
263       }
264     };
265     try {
266       iter.hasNext();
267       fail();
268     } catch (IllegalStateException expected) {
269     }
270   }
271 
272   // Technically we should test other reentrant scenarios (9 combinations of
273   // hasNext/next/peek), but we'll cop out for now, knowing that peek() and
274   // next() both start by invoking hasNext() anyway.
275 
276   /**
277    * Throws a undeclared checked exception.
278    */
279   private static void sneakyThrow(Throwable t) {
280     class SneakyThrower<T extends Throwable> {
281       @SuppressWarnings("unchecked") // not really safe, but that's the point
282       void throwIt(Throwable t) throws T {
283         throw (T) t;
284       }
285     }
286     new SneakyThrower<Error>().throwIt(t);
287   }
288 
289   private static class SomeCheckedException extends Exception {
290   }
291 
292   private static class SomeUncheckedException extends RuntimeException {
293   }
294 }